﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.Threading;
using Sinacor.OMM.DumpEntity;
using Sinacor.OMM.Logging;

namespace Sinacor.OMM.DumpServer
{
    public class FlatFileWriter
    {
        private const string PULA_LINHA = "\r\n";
        private byte[] _pulaLinhaByteArray;

        private Mutex _mutex = new Mutex();
        private FileStream fs;

        private string _path;
        private string _fileName;
        private string _fileNameDated = string.Empty;
        private DumpType.Type _dumpType;
        private DateTime _lastDumpedDataTimeStamp;
        private string _key = string.Empty;
        private bool _closing = false;


        public FlatFileWriter(string path, string fileName, DumpType.Type dumpType)
        {
            this._path = path;
            this._fileName = fileName;
            this._dumpType = dumpType;
            
            _key = GenerateKey(path, fileName, dumpType);

            _pulaLinhaByteArray = Encoding.Default.GetBytes(PULA_LINHA);
        }

        private void DeterminaPath()
        {
            string rootPath = DumpConfiguration.DumpConfigurationManager.DumperConfiguration.RootPath;
            string date = DateTime.Now.ToString("yyyyMMdd");

            if (!rootPath.EndsWith(@"\"))
                rootPath += @"\" ;

            rootPath += date + @"\";

            if (!_path.EndsWith(@"\"))
                _path = rootPath + _path + @"\";

            if (!Directory.Exists(_path))
                Directory.CreateDirectory(_path);
        }

        private void DeterminaNomeArquivo(DateTime timeStamp)
        {
            if (_fileName.Contains(":"))
                _fileName = _fileName.Replace(":", "-");

            if (!_fileName.EndsWith(@".log"))
            {
                _fileNameDated = _fileName;
                _fileNameDated += timeStamp.ToString(" yyyy-MM-dd");
                _fileNameDated += @".log";
            }
            else // Se termina com .log
            {
                _fileNameDated = _fileName.Substring(0, _fileName.Length - 4);
                _fileNameDated += timeStamp.ToString(" yyyy-MM-dd");
                _fileNameDated += @".log";
            }
        }

        public bool Write(byte[] expression, DateTime timeStamp)
        {
            bool ret = false;

            if (!_closing)
            {
                ret = InternalTrace(expression, timeStamp);
            }

            return ret;
        }

        public bool Write(string expression, DateTime timeStamp)
        {
            bool ret = false;

            if (!_closing)
            {
                ret = InternalTrace(Encoding.Default.GetBytes(expression), timeStamp);
            }

            return ret;
        }

        private bool InternalTrace(byte[] expression, DateTime timeStamp)
        {
            bool ret = false;

            if (!_closing)
            {
                if(_mutex !=null)
                    _mutex.WaitOne();

                //Se mudar o dia e tiver que trocar o nome do arquivo ou se for o primeiro write no arquivo

                if (timeStamp.Day != _lastDumpedDataTimeStamp.Day || fs == null)
                {
                    if (fs != null)
                    {   // Fecha o arquivo anterior que estava aberto
                        fs.Flush();
                        fs.Close();
                        fs.Dispose();
                        fs = null;
                    }

                    DeterminaPath();
                    DeterminaNomeArquivo(timeStamp);

                    fs = new FileStream(_path + _fileNameDated, FileMode.Append);
                }

                if (fs != null)
                {
                    lock (fs)
                    {
                        try
                        {
                            // Faz o dump colocando o timestamp a cada entrada e pulando linha
                            if (_dumpType == DumpType.Type.Trace)
                            {
                                string expTimeStamp = timeStamp.ToString("yyyy-MM-dd HH:mm:ss.ffff") + " -> ";
                                fs.Write(Encoding.Default.GetBytes(expTimeStamp), 0, expTimeStamp.Length);
                                fs.Write(expression, 0, expression.Length);
                                fs.Write(_pulaLinhaByteArray, 0, _pulaLinhaByteArray.Length);
                                fs.Flush();
                            }
                            // Faz o dump pulando linha a cada entrada
                            else if (_dumpType == DumpType.Type.DumpLine)
                            {
                                fs.Write(expression, 0, expression.Length);
                                fs.Write(_pulaLinhaByteArray, 0, _pulaLinhaByteArray.Length);
                                fs.Flush();
                            }
                            // Faz o dump do array de bytes sem nenhuma modificação.
                            else if (_dumpType == DumpType.Type.Stream)
                            {
                                fs.Write(expression, 0, expression.Length);
                                fs.Flush();
                            }
                            else
                            {
                                try
                                {
                                    FileStream fsBackup = new FileStream(_path + "UNKNOWTYPE " + _fileNameDated, FileMode.OpenOrCreate);
                                    fsBackup.Write(expression, 0, expression.Length);
                                    fsBackup.Flush();
                                    fsBackup.Close();
                                    fsBackup.Dispose();
                                }
                                catch (Exception ex)
                                {
                                    LogEventViewer("OMM", "Application", Encoding.Default.GetString(expression) + "\r\n" + ex.StackTrace, EventLogEntryType.Error, 9999);
                                }
                            }

                            _lastDumpedDataTimeStamp = timeStamp;
                            ret = true;
                        }
                        catch (Exception)
                        {
                            try
                            {
                                FileStream fsBackup = new FileStream(_path + "LOST " + _fileNameDated, FileMode.OpenOrCreate);
                                fsBackup.Write(expression, 0, expression.Length);
                                fsBackup.Flush();
                                fsBackup.Close();
                                fsBackup.Dispose();
                            }
                            catch (Exception ex)
                            {
                                LogEventViewer("OMM", "Application", Encoding.Default.GetString(expression) + "\r\n" + ex.StackTrace, EventLogEntryType.Error, 9999);
                            }
                        }
                    }
                }
                else
                {
                    EventLog.WriteEntry("Dumper não está conseguindo gravar mensagem", LogSources.DumperService, EventLogEntryType.Warning);
                    ret = false;
                }

                if (_mutex != null)
                    _mutex.ReleaseMutex();
            }

            return ret;
        }

        public static string GenerateKey(string sPath, string sFileName, DumpType.Type dumpType)
        {
            string ret;
            ret = sPath + sFileName + DumpType.ConvertToInt(dumpType).ToString();
            return ret;
        }

        public DateTime LastDumpedDataTimeStamp
        {
            get { return _lastDumpedDataTimeStamp; }
        }

        public void Close()
        {
            //this.Trace("Close called, this is free!");
            _closing = true;

            if(_mutex !=null)
                _mutex.WaitOne();

            if (fs != null)
            {
                lock (fs)
                {
                    fs.Flush();
                    fs.Close();
                    fs.Dispose();
                }
            }

            if (_mutex != null)
            {
                _mutex.ReleaseMutex();
                _mutex.Close();
                _mutex = null;
            }
        }
        
        public string Key
        {
            get { return _key; }
            set { _key = value; }
        }

        public void LogEventViewer(string sSource, string sLog, string sEvent, EventLogEntryType eType, int eventid)
        {
            if (!EventLog.SourceExists(sSource))
                EventLog.CreateEventSource(sSource, sLog);

            EventLog.WriteEntry(sSource, sEvent);
            EventLog.WriteEntry(sSource, sEvent, eType, eventid);
        }


    }
}
